//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <cthread.h>
#include <prxstub.h>
#include <marshal.h>
#include <rot.h>
#include "pseudo.h"
#include <aura.h>
#include <sharecls.h>
#include <pcentral.h>
#include "servicekit.h"

extern UInt32 s_tlsCurrentThreadIndex;

WStringBuf_<32> s_processServiceName;
IStub *s_pCurrentprocessStub = NULL;
STATIC IProcess *      s_pCurrentProcess    = NULL;
STATIC ProcessId       s_currentProcessId   = AURA_INVALID_PID;

typedef struct BufLinkNode : DLinkNode{
    void *buffer;
} BufLinkNode;

STATIC void ProcessRequest(Aura_ChannelId channelId)
{
    ECode     ec = NOERROR;
    PVoid     bufBase = NULL;
    PVoid     tempBuf = NULL;
    PVoid     biggerBuf = NULL;
    DLinkNode *bufLink = NULL;
    UInt32    requestType;

    CIClassInfo   *pClsInfo = NULL;
    EMuid         *pClsId   = NULL;
    MarshalHeader *pheader  = NULL;
    ExportObject  ex;

    Boolean bMoreFlag = FALSE;
    PVoid    pMetaBuf = NULL;

    bufBase = Aura_malloc(MALLOC_SIZE);
    if (NULL == bufBase) {
        goto ErrorExit;
    }

    while (TRUE) {
        ec = (ECode)AuraIpcChannel_Read(channelId,(PVoid)bufBase, MALLOC_SIZE);
        if (ec == E_BUFFER_TOO_SMALL) {
            //if the buffer size is too small,
            //malloc some another buffer to read the data left in the pipe
            //and put them into a buffer link
            bufLink = new BufLinkNode;
            bufLink->Initialize();
            BufLinkNode *pbufNode = new BufLinkNode;
            pbufNode->buffer = bufBase;
            bufLink->InsertLast(pbufNode);

            Int32 nTotalSize = MALLOC_SIZE;
            do {
                tempBuf = Aura_malloc(MALLOC_SIZE);

                ec = (ECode)AuraIpcChannel_Read(channelId, (PVoid)tempBuf, MALLOC_SIZE);

                pbufNode = new BufLinkNode;
                pbufNode->buffer = tempBuf;
                bufLink->InsertLast(pbufNode);

                nTotalSize += MALLOC_SIZE;
            } while (ec == E_BUFFER_TOO_SMALL);

            //merge data in the buffer link into a bigger buffer
            if (ec == NOERROR) {
                biggerBuf = Aura_malloc((Aura_Int32)nTotalSize);
                char *q = (char *)biggerBuf;
                BufLinkNode *p = NULL;
                ForEachDLinkNode(BufLinkNode *, p, bufLink) {
                    Aura_memcpy(q, p->buffer, MALLOC_SIZE);
                    q += MALLOC_SIZE;
                }
                tempBuf = bufBase;
                bufBase = biggerBuf;
            }
        }

        if (FAILED(ec)) {
            goto ErrorExit;
        }

        GET_REQUEST_TYPE(bufBase, requestType);
        switch (requestType) {
        case Request_MetaData:
            pClsId = (EMuid*)((UInt32*)bufBase + 1);
            ec = LookupSharedClassInfo(*pClsId, &pClsInfo);
            if (FAILED(ec)) {
                goto SendECode;
            }

            if (pClsInfo->totalSize >= MALLOC_SIZE) {
                pMetaBuf = Aura_malloc(pClsInfo->totalSize
                                       + sizeof(UInt32)
                                       + sizeof(ECode));
                if (NULL == pMetaBuf) {
                     ec = E_OUT_OF_MEMORY;
                     goto SendECode;
                }
                bMoreFlag = TRUE;
            }
            else {
                pMetaBuf = bufBase;
            }

            SET_REPLY_TYPE(pMetaBuf, Reply_MetaData);
            SET_REPLY_ECODE(pMetaBuf, ec);
            UnrelocateClassInfo(pClsInfo, (CIClassInfo*)BY_REFERENCE(pMetaBuf,2));


            ec = (ECode)AuraIpcChannel_AsyncWrite(channelId, pMetaBuf,
                                 REPLY_BUF_SIZE(pClsInfo->totalSize));
            if (bMoreFlag) {
                Aura_free(pMetaBuf);
                pMetaBuf = NULL;
                bMoreFlag = FALSE;
            }

            if (FAILED(ec)) {
                goto ErrorExit;
            }
            break;

       case Request_MethodCall:
            pheader = (MarshalHeader*)BY_REFERENCE(bufBase,1);
            ec = FindExportObject(pheader->m_oid, &ex);
            if (FAILED(ec)) {
                goto SendECode;
            }
			ex.m_pIStub->Release();
            ex.m_pIStub->Invoke(channelId, (void*)pheader, pheader->m_uInSize);           
            break;

        default:
            assert(0);
            break;
        }
        if (biggerBuf) {
            //bigger buffer used, resume the old one
            //and clear the buffer link
            bufBase = tempBuf;
            Aura_free(biggerBuf);
            biggerBuf = NULL;
            tempBuf = NULL;
            BufLinkNode *p = NULL;
            while(!bufLink->IsEmpty()) {
                p = (BufLinkNode *)(bufLink->Prev());
                if (bufLink->First() != p) {
                    //first node contains bufBase pointer, no need to free
                    Aura_free(p->buffer);
                }
                p->Detach();
                delete p;
            }
            delete bufLink;
            bufLink = NULL;
        }
        continue;

    SendECode:
        UInt32 uData[2];
        SET_REPLY_TYPE(uData, Reply_ECodeOnly);
        SET_REPLY_ECODE(uData, ec);
        AuraIpcChannel_AsyncWrite(channelId, uData, REPLY_BUF_SIZE(0));

        if (biggerBuf) {
            //bigger buffer used, resume the old one
            //and clear the buffer link
            bufBase = tempBuf;
            Aura_free(biggerBuf);
            biggerBuf = NULL;
            tempBuf = NULL;
            BufLinkNode *p = NULL;
            while(!bufLink->IsEmpty()) {
                p = (BufLinkNode *)(bufLink->Prev());
                if (bufLink->First() != p) {
                    //first node contains bufBase pointer, no need to free
                    Aura_free(p->buffer);
                }
                p->Detach();
                delete p;
            }
            delete bufLink;
            bufLink = NULL;
        }
    }

ErrorExit:
    CServiceKit::S_DeleteKit(channelId);
    if (NULL != bufBase) {
        Aura_free(bufBase);
        bufBase = NULL;
    }
    return ;
}

STATIC void _ProcessEntry(PVoid pArgs)
{
    IThread * pThread = (IThread *)pArgs;
    Address userEntry = 0;
    Address userArgs  = 0;
    ECode ec = NOERROR;

    ((CThread *)pThread)->GetUserEntry(&userEntry, &userArgs);
    ec = (ECode)AuraTls_SetValue((Aura_UInt32)s_tlsCurrentThreadIndex, (PVoid)pThread);
    assert(SUCCEEDED(ec));

    ProcessRequest((Aura_ChannelId)userArgs);

    return ;
}


STATIC void _ServiceEntry(PVoid arg)
{
#define INVALID_CHANNEL_ID  ((Aura_ChannelId)-1)

    WStringBuf_<32> channelName;
    Aura_ChannelId  listenId  = INVALID_CHANNEL_ID;
    ECode ec = NOERROR;

    channelName.Append((Int32)s_currentProcessId);

    ec = (ECode)AuraIpcChannel_Create((Aura_PWChar)(const wchar_t *)channelName, &listenId);
    if (FAILED(ec)) {
        goto ErrorExit;
    }


    while (TRUE) {
        Aura_ChannelId clientId = INVALID_CHANNEL_ID;
        IThread * pThread  = NULL;

        ec = (ECode)AuraIpcChannel_WaitForConnection(
                                (Aura_PWChar)(const wchar_t *)channelName,
                                &listenId,
                                &clientId);
        if (FAILED(ec)) {
            continue;
        }

        ec = CThread::S_Create(s_pCurrentProcess,
                            (Address)_ProcessEntry,
                            (Address)_ProcessEntry,
                            (Address)clientId,
                            ThreadCreationFlag_Started,
                            &pThread);
        if (FAILED(ec)) {
            AuraIpcChannel_Destroy(clientId);
            continue;
        }
        pThread->Release();
        ec = CServiceKit::S_CreateKit(clientId, (IThread *)pThread);
        if (FAILED(ec)) { // BUG for backward the state if failed.
            continue;
        }
    }

    AuraIpcChannel_Destroy(listenId);
    //thread will not return here until the process exit,
    //so no need to release
    return ;

ErrorExit:
    if (INVALID_CHANNEL_ID != listenId) {
        AuraIpcChannel_Destroy(listenId);
    }

    return ;
}

EXTERN_C ECode InitService(void)
{
    ECode ec = NOERROR;

    ec = CServiceKit::S_Create();
    if (FAILED(ec)) {
        goto ErrorExit;
    }

    CPseudoKernel::Process_GetCurrentId(&s_currentProcessId);
    CPseudoKernel::Process_GetCurrent(&s_pCurrentProcess);

    s_processServiceName.Copy(ELAURA_PROCESS_PREFIX_NAME);
    s_processServiceName.Append((Int32)s_currentProcessId);

    ec = CPseudoSyscall::RegisterRunningObject(s_processServiceName,
                                         (PInterface)s_pCurrentProcess);
    if (FAILED(ec)) {
        goto ErrorExit;
    }

    ec = AuraThread_Create(_ServiceEntry, NULL, NULL, NULL, NULL);
    if (FAILED(ec)) {
        CPseudoSyscall::UnregisterRunningObject(s_processServiceName);
        goto ErrorExit;
    }
    return NOERROR;

ErrorExit:
    if (NULL != s_pCurrentProcess) {
        s_pCurrentProcess->Release();
        s_pCurrentProcess = NULL;
    }
    CServiceKit::S_Destroy();
    return ec;
}

EXTERN_C void DestroyService(void)
{
    CServiceKit::S_Destroy();
    CPseudoSyscall::UnregisterRunningObject(s_processServiceName);

    if (NULL != s_pCurrentprocessStub) {
        s_pCurrentprocessStub->Release();
    }
    if (NULL != s_pCurrentProcess) {
       s_pCurrentProcess->Release();
       s_pCurrentProcess = NULL;
    }

    return ;
}

